/*
 * Copyright 2021-2022 the original author(https://github.com/wj596)
 * 
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * </p>
 */
package org.jsets.fastboot.security.config;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.PostConstruct;
import org.jsets.fastboot.util.SpringContextHolder;
import org.jsets.fastboot.util.StringUtils;
import org.jsets.fastboot.security.ICaptchaProvider;
import org.jsets.fastboot.security.SecurityUtils;
import org.jsets.fastboot.security.aop.AnnotationAuthAspect;
import org.jsets.fastboot.security.authc.AuthcExceptionHandlerImpl;
import org.jsets.fastboot.security.authc.AuthenticatorImpl;
import org.jsets.fastboot.security.authc.IAuthcExceptionHandler;
import org.jsets.fastboot.security.authc.IAuthenticator;
import org.jsets.fastboot.security.authc.ILoginHandler;
import org.jsets.fastboot.security.authc.IPasswdRetryHandler;
import org.jsets.fastboot.security.authc.LoginHandlerImpl;
import org.jsets.fastboot.security.authc.LoginHandlerManager;
import org.jsets.fastboot.security.authc.MD5PasswordEncoderImpl;
import org.jsets.fastboot.security.authc.PasswdRetryWatcher;
import org.jsets.fastboot.security.authc.SM3PasswordEncoderImpl;
import org.jsets.fastboot.security.authc.Sha256PasswordEncoderImpl;
import org.jsets.fastboot.security.authz.AuthorizerImpl;
import org.jsets.fastboot.security.authz.AuthzExceptionHandlerImpl;
import org.jsets.fastboot.security.authz.AuthzRuleManager;
import org.jsets.fastboot.security.authz.IAuthorizer;
import org.jsets.fastboot.security.authz.IAuthzExceptionHandler;
import org.jsets.fastboot.security.cache.ICacheManager;
import org.jsets.fastboot.security.cache.MapCacheManager;
import org.jsets.fastboot.security.cache.RedisCacheManager;
import org.jsets.fastboot.security.captcha.CaptchaProviderImpl;
import org.jsets.fastboot.security.filter.CorsFilter;
import org.jsets.fastboot.security.filter.TokenAuthcFilter;
import org.jsets.fastboot.security.filter.TokenAuthzFilter;
import org.jsets.fastboot.security.listener.IListenerManager;
import org.jsets.fastboot.security.online.OnlineManager;
import org.jsets.fastboot.security.rememberme.RememberMeManager;
import org.jsets.fastboot.security.token.ITokenManager;
import org.jsets.fastboot.security.token.TokenManagerImpl;

/**
 * Security配置
 *
 * @author engage (https://github.com/wj596)
 * @date 2021.07.12 21:49
 * @since 0.1
 */
@Slf4j
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnProperty(prefix = Constants.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = false)
public class SecurityAutoConfiguration {

	@Autowired
	private SecurityProperties properties;
	@Autowired
	private ApplicationContext applicationContext;
	@Autowired
	private Environment environment;

	@PostConstruct
	public void initialize() {
		SpringContextHolder.setApplicationContextIfNecessary(this.applicationContext);
		SpringContextHolder.setEnvironmentIfNecessary(environment);
		SecurityUtils.setProperties(this.properties);
	}

	@Bean
	@ConditionalOnMissingBean
	public ICacheManager securityCacheManager() {
		String cacheType = this.environment.getProperty(Constants.SPRING_CACHE_TYPE, "");
		String redisHost = this.environment.getProperty(Constants.SPRING_REDIS_HOST, "");
		ICacheManager cacheManager = null;
		if (Constants.CACHE_TYPE_REDIS.equals(cacheType.toLowerCase()) || StringUtils.notEmpty(redisHost)) {
			log.info("缓存类型： Redis");
			RedisConnectionFactory connectionFactory = this.applicationContext.getBean(RedisConnectionFactory.class);
			cacheManager = new RedisCacheManager(connectionFactory);
		} else {
			log.info("缓存类型： Map");
			cacheManager = new MapCacheManager();
		}
		return cacheManager;
	}

	@Bean
	@ConditionalOnMissingBean
	public ITokenManager tokenManagerImpl(ICacheManager securityCacheManager) {
		ITokenManager tokenManager = new TokenManagerImpl(this.properties, securityCacheManager);
		return tokenManager;
	}

	@Bean
	public IListenerManager securityListenerManager() {
		IListenerManager manager = new IListenerManager();
		SecurityUtils.setListenerManager(manager);
		return manager;
	}

	@Bean
	@ConditionalOnMissingBean
	public RememberMeManager rememberMeManager() {
		RememberMeManager rememberMeManager = new RememberMeManager(this.properties);
		return rememberMeManager;
	}

	@Bean
	@ConditionalOnMissingBean
	public OnlineManager onlineManager(ICacheManager securityCacheManager, ITokenManager tokenManager) {
		OnlineManager onlineManager = new OnlineManager(this.properties, securityCacheManager, tokenManager);
		return onlineManager;
	}

	@Bean
	public AuthzRuleManager authzRuleManager() {
		AuthzRuleManager authzRuleManager = new AuthzRuleManager();
		SecurityUtils.setAuthzRuleManager(authzRuleManager);
		return authzRuleManager;
	}

	@Bean
	public ILoginHandler loginHandlerImpl() {
		return new LoginHandlerImpl();
	}

	@Bean
	public LoginHandlerManager loginHandlerManager() {
		LoginHandlerManager loginHandlerManager = new LoginHandlerManager();
		SecurityUtils.setLoginHandlerManager(loginHandlerManager);
		return loginHandlerManager;
	}

	@Bean
	@ConditionalOnMissingBean
	public ICaptchaProvider captchaProviderImpl(SecurityProperties properties, ICacheManager cacheManager) {
		ICaptchaProvider captchaProvider = new CaptchaProviderImpl(properties, cacheManager);
		return captchaProvider;
	}

	@Bean
	@ConditionalOnMissingBean
	public IAuthcExceptionHandler authcExceptionHandlerImpl() {
		return new AuthcExceptionHandlerImpl();
	}

	@Bean
	@ConditionalOnMissingBean
	public IAuthzExceptionHandler authzExceptionHandlerImpl() {
		return new AuthzExceptionHandlerImpl();
	}

	@Bean
	public PasswordEncoder passwordEncoder(SecurityProperties properties) {
		PasswordEncoder passwordEncoder = null;
		String algorithm = properties.getPasswordEncodeAlgorithm();
		log.info("密码编码算法[{}]", algorithm);
		switch (algorithm) {
		case Constants.PASSWORD_ENCODE_ALGORITHM_SHA256:
			passwordEncoder = new Sha256PasswordEncoderImpl();
			break;
		case Constants.PASSWORD_ENCODE_ALGORITHM_SM3:
			passwordEncoder = new SM3PasswordEncoderImpl();
			break;
		default:
			passwordEncoder = new MD5PasswordEncoderImpl();
			break;
		}
		return passwordEncoder;
	}

	@Bean
	public PasswdRetryWatcher passwdRetryWatcher(SecurityProperties properties, ICacheManager cacheManager, ObjectProvider<IPasswdRetryHandler> handlerProvider) {
		PasswdRetryWatcher passwdRetryWatcher = new PasswdRetryWatcher(properties, handlerProvider.getIfAvailable(), cacheManager);
		SecurityUtils.setPasswdRetryWatcher(passwdRetryWatcher);
		return passwdRetryWatcher;
	}

	@Bean
	@ConditionalOnMissingBean
	public IAuthenticator authenticatorImpl() {
		IAuthenticator authenticator = new AuthenticatorImpl();
		return authenticator;
	}

	@Bean
	@ConditionalOnMissingBean
	public IAuthorizer authorizerImpl() {
		IAuthorizer authorizer = new AuthorizerImpl();
		return authorizer;
	}

	@Bean
	@ConditionalOnProperty(prefix = Constants.PREFIX, name = "cors-enabled", havingValue = "true")
	public FilterRegistrationBean<CorsFilter> corsFilterRegister() {
		log.info("实例化CorsFilter");
		FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<CorsFilter>();
		CorsFilter corsFilter = new CorsFilter();
		registration.setFilter(corsFilter);
		registration.addUrlPatterns("/*");
		registration.setName("corsFilter");
		registration.setOrder(Constants.CORS_FILTER_ORDER);
		return registration;
	}

	@Bean
	public FilterRegistrationBean<TokenAuthcFilter> tokenAuthcFilterRegister(SecurityProperties properties, IAuthcExceptionHandler exceptionHandler) {
		FilterRegistrationBean<TokenAuthcFilter> register = new FilterRegistrationBean<TokenAuthcFilter>();
		TokenAuthcFilter tokenAuthcFilter = new TokenAuthcFilter(exceptionHandler);
		register.setName("TokenAuthcFilter");
		log.info("Register TokenAuthcFilter[TokenAuthcFilter]");
		register.setFilter(tokenAuthcFilter);
		register.addUrlPatterns("/*");
		register.setOrder(Constants.TOKEN_AUTHC_FILTER_ORDER);
		return register;
	}

	@Bean
	@ConditionalOnProperty(prefix = Constants.PREFIX, name = "authz-filter-enabled", havingValue = "true", matchIfMissing = false)
	public FilterRegistrationBean<TokenAuthzFilter> tokenAuthzFilterRegister(IAuthzExceptionHandler exceptionHandler) {
		log.info("启用鉴权拦截器");
		FilterRegistrationBean<TokenAuthzFilter> register = new FilterRegistrationBean<TokenAuthzFilter>();
		TokenAuthzFilter securityFilter = new TokenAuthzFilter(exceptionHandler);
		register.setFilter(securityFilter);
		register.addUrlPatterns("/*");
		register.setName("TokenAuthzFilter");
		register.setOrder(Constants.TOKEN_AUTHZ_FILTER_ORDER);
		return register;
	}

	@Bean
	@ConditionalOnProperty(prefix = Constants.PREFIX, name = "annotation-enabled", havingValue = "true")
	public AnnotationAuthAspect annotationAuthAspect() {
		return new AnnotationAuthAspect();
	}

	@Bean
	public SecurtityCommandLineRunner securtityCommandLineRunner() {
		return new SecurtityCommandLineRunner();
	}

}